<?php

namespace backend\components;

use Yii;
use yii\base\ErrorException;
use yii\db\Exception;
use yii\helpers\FileHelper;
use backend\models\Extensions;
use backend\models\Clients;
use common\components\Helper;
use wapmorgan\UnifiedArchive\UnifiedArchive;

/**
 * Description of Installer
 * 安装扩展
 * @author fireloong
 */
class Installer
{
    /**
     * @var array Array of paths needed by the installer
     */
    protected $paths = [];

    /**
     * @var boolean True if package is an upgrade
     */
//    protected $upgrade = null;

    /**
     * @var array Associative array of adapters
     */
    protected $_adapters = [];

    /**
     * @var string 扩展资源解压路径
     */
    public $source;

    /**
     * @var string 上传扩展压缩包文件路径
     */
    protected $packagefile;
    public $manifest;
    public $manifestFile;
    public $alert;
    /**
     * Stack of installation steps
     * - Used for installation rollback
     * @var array
     */
    protected $stepStack = [];

    /**
     * @var bool Flag if the uninstall process was triggered by uninstalling a package
     */
    protected $packageUninstall = false;

    /**
     * 安装扩展
     * @param string $filename 扩展路径
     * @return array|boolean 安装成功返回相关信息数组，否则返回 FALSE。
     * @throws ErrorException
     */
    public function install($filename = '')
    {
        $session = Yii::$app->session;
        if (empty($filename)) {
            if ($session->has('source') && $session->has('packagefile')) {
                $this->source = $session->get('source');
                $this->packagefile = $session->get('packagefile');
                if (is_dir($this->source) && is_file($this->packagefile)) {
                    $retval = $this->unpack();
                } else {
                    $session->addFlash('error', Yii::t('installer', 'INSTALL_PACKAGE_FILE_DIRECTORIES_NOT_EXIST'));
                    return false;
                }
            } else {
                return false;
            }
        } else {
            if (is_file($filename)) {
                $retval = $this->unpack($filename, true);
            } else {
                $retval = $this->getRetval($filename, null);
            }

            if (false === $retval) {
                FileHelper::unlink($filename);
                return false;
            }
            if (false === $retval['type']) {
                FileHelper::removeDirectory($retval['extractdir']);
                FileHelper::unlink($retval['packagefile']);
                return false;
            }
            $this->source = $retval['extractdir'];
            $this->packagefile = $retval['packagefile'];
        }
        $type = strtolower($retval['type']);
        $this->getManifest();
        $params = ['route' => 'install', 'manifest' => $this->manifest];
        $adapter = $this->getAdapter($type, $params);

        if (is_object($adapter)) {
            $res = $adapter->install();
        } else {
            $params = [
                'action' => Yii::t('installer', 'INSTALL'),
                'error' => Yii::t('installer', 'ERROR_UNKNOWN_TYPE')
            ];
            $session->addFlash('error', Yii::t('installer', 'ABORT_ROLLBACK', $params));
            $res = false;
        }

        if (!$session->has('source') || !$session->has('packagefile')) {
            FileHelper::removeDirectory($retval['extractdir']);
            if (!is_null($retval['packagefile'])) {
                FileHelper::unlink($retval['packagefile']);
            }
        }

        return $res;
    }

    /**
     * 卸载扩展
     * @param array $ids 扩展ID
     * @return boolean 成功返回 TRUE，失败返回 FALSE
     */
    public function uninstall($ids)
    {
        $session = Yii::$app->session;
        $extensions = Extensions::findAll($ids);
        $result = false;
        foreach ($extensions as $extension) {
            $params = ['extension' => $extension, 'route' => 'uninstall'];
            $adapter = $this->getAdapter($extension->type, $params);
            $typeMsg = Yii::t('installer', strtoupper($extension->type));
            if (!is_object($adapter)) {
                $msg = Yii::t('installer', 'ERROR_UNINSTALLING_TYPE', $typeMsg);
                $session->addFlash('error', $msg);
                continue;
            }

            $result = $adapter->uninstall($extension);

            if ($result) {
                $msg = Yii::t('installer', 'UNINSTALLING_TYPE_SUCCESSFUL', $typeMsg);
                $session->addFlash('success', $msg);
                $result = true;
                continue;
            } else {
                $msg = Yii::t('installer', 'ERROR_UNINSTALLING_TYPE', $typeMsg);
                $session->addFlash('error', $msg);
                continue;
            }
        }
        return $result;
    }

    /**
     * Get whether this installer is uninstalling extensions which are part of a package
     * @return bool
     */
    public function isPackageUninstall()
    {
        return $this->packageUninstall;
    }

    /**
     * Set whether this installer is uninstalling extensions which are part of a package
     * @param boolean $uninstall True if a package triggered the uninstall, false otherwise
     */
    public function setPackageUninstall($uninstall)
    {
        $this->packageUninstall = $uninstall;
    }

    /**
     * 解压扩展压缩包
     * @param string $filename 扩展压缩包路径
     * @return array|boolean 成功返回相关信息数组，否则返回 FALSE。
     * @throws \Exception
     */
    public function unpack($filename = '')
    {
        if (empty($filename)) {
            return $this->getRetval($this->source, $this->packagefile);
        }
        $tmpdir = uniqid('install_');
        $extractdir = FileHelper::normalizePath(dirname($filename) . '/' . $tmpdir);
        $archivename = realpath($filename);
        $archive = UnifiedArchive::open($archivename);
        $files = $archive->getFileNames();
        $pattern = '/[\w\/\-\.#\+]+/';
        $unfiles = [];
        foreach ($files as $file) {
            preg_match($pattern, $file, $matches);
            if ($file !== $matches[0]) {
                $unfiles[] = $file;
            }
        }
        if (empty($unfiles)) {
            $result = $archive->extractFiles($extractdir);
            if (is_int($result) && $result > 0) {
                return $this->getRetval($extractdir, $archivename);
            }
        } else {
            $message = Yii::t('installer', 'FILENAMES_FORMAT_INCORRECT_FILES', [
                'files' => implode('<br>', $unfiles)
            ]);
            Yii::$app->session->addFlash('error', $message);
        }
        return false;
    }

    /**
     * 获取扩展摘要信息数组
     * @param string $extractDir 扩展解压目录路径
     * @param string $archiveName 上传的压缩包文件路径
     * @return mixed array
     */
    private function getRetval($extractDir, $archiveName)
    {
        $retval['extractdir'] = $extractDir;
        $retval['packagefile'] = $archiveName;
        $foldersOne = FileHelper::findDirectories($extractDir, ['recursive' => false]);
        $filesOne = FileHelper::findFiles($extractDir, ['recursive' => false]);
        $dirList = array_merge($foldersOne, $filesOne);
        if (count($dirList) === 1 && is_dir($dirList[0])) {
            $extractDir = $dirList[0];
        }
        $retval['dir'] = $extractDir;
        $retval['type'] = $this->detectType($extractDir);
        return $retval;
    }

    /**
     * 查明扩展类型
     * @param string $dir 解压后的扩展目录
     * @param boolean $returnXmlObj 是否返回 XML 对象
     * @return boolean|object|string 成功返回相关信息的数组或对象，否则返回 FALSE。
     */
    public function detectType($dir, $returnXmlObj = false)
    {
        $files = FileHelper::findFiles($dir, ['only' => ['*.xml'], 'recursive' => false]);
        if (empty($files)) {
            Yii::$app->session->addFlash('error', Yii::t('installer', 'ERROR_NOT_FIND_XML_SETUP_FILE'));
            return false;
        }

        foreach ($files as $file) {
            $xml = simplexml_load_file($file);
            if (!$xml) {
                continue;
            }
            $name = $xml->getName();
            if ($name !== 'extension' && $name !== 'metafile') {
                unset($xml);
                continue;
            }
            $type = (string)$xml->attributes()->type;
            if ($returnXmlObj) {
                return $this->parseXMLInstall($xml, $file);
            } else {
                unset($xml);
                return $type;
            }
        }
        Yii::$app->session->addFlash('error', Yii::t('installer', 'ERROR_NOT_FIND_XML_SETUP_FILE'));
        return false;
    }

    /**
     * 解析 XML 安装文件
     * @param \SimpleXMLElement $xml XML 对象
     * @param string $path XML 文件路径
     * @return array 返回相关信息数组
     */
    private function parseXMLInstall(\SimpleXMLElement $xml, $path)
    {
        $data = [];
        $data['name'] = (string)$xml->name;
        $data['type'] = (string)$xml->attributes()->type;
        $data['creationDate'] = ((string)$xml->creationDate) ?: 'Unknown';
        $data['author'] = ((string)$xml->author) ?: 'Unknown';
        $data['copyright'] = (string)$xml->copyright;
        $data['authorEmail'] = (string)$xml->authorEmail;
        $data['authorUrl'] = (string)$xml->authorUrl;
        $data['version'] = (string)$xml->version;
        $data['description'] = trim((string)$xml->description);
        $data['group'] = (string)$xml->attributes()->group;
        $data['filename'] = basename($path, '.xml');
        if ($xml->backend && isset($xml->backend->messages) && $xml->backend->messages->attributes()->main) {
            $data['langCat'] = (string)$xml->backend->messages->attributes()->main;
        } elseif (isset($xml->messages)) {
            $data['langCat'] = (string)($xml->messages->attributes()->main ?: $xml->name);
        }
        return $data;
    }

    /**
     * Get the upgrade switch
     * @return bool
     */
    public function isUpgrade()
    {
        return (string)$this->manifest->attributes()->method === 'upgrade';
    }

    /**
     * 获取 XML 设置文件
     * @return boolean
     */
    public function getManifest()
    {
        if (!is_object($this->manifest)) {
            return $this->findManifest();
        }
        return $this->manifest;
    }

    /**
     * 查找 XML 设置文件
     * @return boolean
     */
    public function findManifest()
    {
        if (!is_dir($this->source) && !is_dir($this->getPath('source'))) {
            return false;
        }

        $this->source = $this->source ?? $this->getPath('source');

        $parentXmlFiles = FileHelper::findFiles($this->source, ['only' => ['*.xml'], 'recursive' => false]);
        $allXmlFiles = FileHelper::findFiles($this->source, ['only' => ['*.xml']]);

        $xmlFiles = array_unique(array_merge($parentXmlFiles, $allXmlFiles));

        if (!empty($xmlFiles)) {
            foreach ($xmlFiles as $file) {
                $manifest = $this->isManifest($file);
                if ($manifest !== false) {
                    $this->manifest = $manifest;
                    $this->manifestFile = $file;
                    $this->source = dirname($file);
                    return true;
                }
            }
        }

        Yii::$app->session->addFlash('error', Yii::t('installer', 'ERROR_NOT_FIND_XML_SETUP_FILE'));
        return false;
    }

    /**
     * 文件是否为有效的安装清单XML文件
     * @param string $file 文件路径
     * @return \SimpleXMLElement|boolean 成功返回 SimpleXMLElement，否则返回 FALSE
     */
    private function isManifest($file)
    {
        $xml = simplexml_load_file($file);
        if (!$xml) {
            return false;
        }
        if ($xml->getName() !== 'extension') {
            return false;
        }
        return $xml;
    }

    /**
     * 获取应用ID
     * @param string $client 应用名称
     * @return int
     */
    public function getClientId($client = null)
    {
        $clientName = $client === null ? (string)$this->manifest->attributes()->client : $client;
        $clients = Clients::findOne(['name' => $clientName]);
        $clientId = $clients === null ? 0 : $clients->id;
        return $clientId;
    }

    /**
     * 存储到扩展数据表中
     * @param array $data 扩展数据
     * @return boolean|int 成功返回 ID，否则返回 FALSE
     */
//    private function storeExtensions($data)
//    {
//        $extensions = Extensions::findOne([
//                    'package_id' => $data['package_id'],
//                    'name' => $data['name'],
//                    'type' => $data['type'],
//                    'element' => $data['element'],
//                    'client_id' => $data['client_id']
//        ]);
//
//        if (!is_object($extensions)) {
//            $extensions = new Extensions();
//        }
//
//        $extensions->attributes = $data;
//
//        if ($extensions->save()) {
//            return $extensions->id;
//        } else {
//            return false;
//        }
//    }

    /**
     * Gets a unique language SEF string.
     * @param string $languageTag Language Tag
     * @return string
     */
//    private function getSefString($languageTag)
//    {
//        $langs = explode('-', $languageTag);
//        $prefixToFind = $langs[0];
//
//        $language = Languages::findOne(['code' => $languageTag]);
//        if ($language === null) {
//            $language = Languages::findOne(['sef' => $prefixToFind]);
//            return $language === null ? $prefixToFind : strtolower($languageTag);
//        } else {
//            return $language->sef;
//        }
//    }

    /**
     * Get the filtered extension element from the manifest
     * @param string $type Extension of type
     * @param string $element Optional element name to be converted
     * @return string  The filtered element
     */
//    private function getElement($type = null, $element = null)
//    {
//        if (!$type) {
//            $type = (string) $this->manifest->attributes()->type;
//        }
//
//        switch ($type) {
//            case 'package':
//                if (!$element) {
//                    $element = (string) $this->manifest->packagename;
//                    $element = 'pkg_' . trim($element);
//                }
//                break;
//            case 'language':
//                if (!$element) {
//                    $element = (string) $this->manifest->tag;
//                }
//                break;
//            default :
//                if (!$element) {
//                    $element = (string) $this->manifest->element;
//                }
//                if (!$element) {
//                    $element = (string) $this->manifest->name;
//                }
//        }
//
//        return $element;
//    }

    /**
     * 通过应用包的 manifest XML 文件中 files 标签解析出子扩展信息
     * @param \SimpleXMLElement $element A SimpleXMLElement from which to load data from
     * @return \stdClass 返回扩展对象
     */
//    private function getExtensionInfo(\SimpleXMLElement $element = null)
//    {
//        if ($element) {
//            $obj = new \stdClass();
//            $obj->type = (string) $element->attributes()->type;
//            $obj->id = (string) $element->attributes()->id;
//            $obj->client = '';
//            $obj->client_id = 0;
//            $obj->group = '';
//            switch ($obj->type) {
//                case 'component':
//                    // By default a component doesn't have anything
//                    break;
//                case 'module':
//                case 'template':
//                case 'language':
//                    $obj->client = (string) $element->attributes()->client;
//                    $tmp_client_id = $this->getClientId($obj->client);
//                    if ($tmp_client_id) {
//                        $obj->client_id = $tmp_client_id;
//                    } else {
//                        $this->alert['warning'][] = Yii::t('installer', 'Invalid client identifier specified in extension manifest.');
//                    }
//                    break;
//                case 'plugin':
//                    $obj->group = (string) $element->attributes()->group;
//                    break;
//                default :
//                    if ($element->attributes()->client) {
//                        $obj->client_id = $this->getClientId();
//                    }
//                    if ($element->attributes()->group) {
//                        $obj->group = (string) $element->attributes()->group;
//                    }
//                    break;
//            }
//            $obj->filename = (string) $element;
//            return $obj;
//        }
//    }
    /**
     * Gets the extension id.
     * @param string $type The extension type.
     * @param string $id The name of the extension (the element field).
     * @param integer $client The client id
     * @param string $group The extension group
     * @return object
     */
//    private function getExtensionObject($type, $id, $client, $group)
//    {
//        $extension = Extensions::findOne([
//                    'type' => $type,
//                    'element' => $id,
//                    'client_id' => $this->getClientId($client),
//                    'folder' => $group
//        ]);
//
//        return $extension;
//    }
    /**
     * Generates a manifest cache
     * @return array serialised manifest data
     */
    public function generateManifestCache()
    {
        return $this->parseXMLInstall($this->manifest, $this->manifestFile);
    }

    /**
     * Method to parse the parameters of an extension, build the JSON string for its default parameters, and return the JSON string.
     * @return string  JSON string of parameter values
     */
    public function getParams()
    {
        if (!isset($this->manifest->backend->config->fields) && !isset($this->manifest->config->fields)) {
            return '';
        }

        $fields = $this->manifest->backend->config->fields ?? $this->manifest->config->fields;

        if (count($fields->field) === 0 && count($fields->fieldset) === 0) {
            return '';
        }

        $ini = [];

        if (count($fields->fieldset) > 0) {
            foreach ($fields->fieldset as $item) {
                if (count($item->field) > 0) {
                    foreach ($item->field as $field) {
                        $name = $field->attributes()->name;
                        $value = $field->attributes()->default;
                        if (is_null($name) || is_null($value)) {
                            continue;
                        }
                        $ini[(string)$name] = (string)$value;
                    }
                }
            }
        } else {
            foreach ($fields->field as $field) {
                $name = $field->attributes()->name;
                $value = $field->attributes()->default;
                if (is_null($name) || is_null($value)) {
                    continue;
                }
                $ini[(string)$name] = (string)$value;
            }
        }

        return empty($ini) ? '' : json_encode($ini);
    }

    /**
     * Pushes a step onto the installer stack for rolling back steps
     * @param array $step Installer step
     */
    public function pushStep($step)
    {
        $this->stepStack[] = $step;
    }

    /**
     * Installation abort method
     * @param string $msg Abort message from the installer
     * @param string $type Package type if defined
     * @return boolean
     * @throws ErrorException
     * @throws Exception
     * @throws \Throwable
     */
    public function abort($msg = null, $type = null)
    {
        $retval = true;
        $step = array_pop($this->stepStack);
        if ($msg) {
            Yii::$app->session->addFlash('error', $msg);
        }
        while ($step != null) {
            switch ($step['type']) {
                case 'file':
                    $stepval = FileHelper::unlink($step['path']);
                    break;
                case 'folder':
                    $stepval = FileHelper::removeDirectory($step['path']);
                    break;
                case 'query':
                    $stepval = $this->parseSQL($step['script']);
                    break;
                case 'extension':
                    try {
                        $extension = Extensions::findOne($step['id']);
                        $extension->delete();
                        $stepval = true;
                    } catch (Exception $e) {
                        $errorMsg = Yii::t('installer', 'ABORT_ERROR_DELETING_EXTENSIONS_RECORD');
                        Yii::$app->session->addFlash('warning', $errorMsg);
                        $stepval = false;
                    }
                    break;
                default:
                    if ($type && is_object($this->_adapters[$type])) {
                        // Build the name of the custom rollback method for the type
                        $method = '_rollback_' . $step['type'];

                        // Custom rollback method handler
                        if (method_exists($this->_adapters[$type], $method)) {
                            $stepval = $this->_adapters[$type]->$method($step);
                        }
                    } else {
                        $stepval = false;
                    }
                    break;
            }
            // Only set the return value if it is false
            if ($stepval === false) {
                $retval = false;
            }
            // Get the next step and continue
            $step = array_pop($this->stepStack);
        }
        return $retval;
    }

    /**
     * 获取适配器
     * @param $name 适配器名称
     * @param array $options 适配器参数
     * @return bool|mixed
     */
    public function getAdapter($name, $options = [])
    {
        $this->getAdapters($options);

        if (!key_exists($name, $this->_adapters)) {
            return false;
        }

        return $this->_adapters[$name];
    }

    /**
     * 获取所有适配器
     * @param array $options 参数
     * @param array $custom 自定义参数
     * @return array
     */
    public function getAdapters($options = [], $custom = [])
    {
        $adapterPath = __DIR__ . DIRECTORY_SEPARATOR . 'adapter' . DIRECTORY_SEPARATOR;
        $rootPath = Yii::getAlias('@root');
        $pos = strpos($adapterPath, $rootPath);
        if ($pos !== 0) {
            return $this->_adapters;
        }
        $classPrefix = str_replace('/', '\\', substr($adapterPath, strlen($rootPath)));

        $files = new \DirectoryIterator($adapterPath);
        foreach ($files as $file) {
            $fileName = $file->getFilename();
            if (!$file->isFile() || $file->getExtension() !== 'php') {
                continue;
            }
            $name = str_ireplace(['.php', 'adapter'], '', trim($fileName));

            $class = $classPrefix . ucfirst($name) . 'Adapter';

            if (!class_exists($class)) {
                $class = $classPrefix . ucfirst($name);
            }
            if (!class_exists($class)) {
                continue;
            }
            $this->_adapters[strtolower($name)] = new $class($this, $options);
        }

        // Add any custom adapters if specified
        if (count($custom) > 0) {
            foreach ($custom as $key => $adapter) {
                if (!class_exists($adapter)) {
                    continue;
                }
                $this->_adapters[$key] = new $adapter($this, $options);
            }
        }

        return $this->_adapters;
    }

    /**
     * Get an installer path by name
     * @param string $name Path name
     * @param string $default Default value
     * @return string  Path
     */
    public function getPath($name, $default = null)
    {
        return $this->paths[$name] ?? $default;
    }

    /**
     * Sets an installer path by name
     * @param string $name Path name
     * @param string $value Path
     */
    public function setPath($name, $value)
    {
        $this->paths[$name] = $value;
    }

    public function findDeletedFiles($oldFiles, $newFiles)
    {
        $folders = $files = $containers = $foldersDeleted = $filesDeleted = [];
        foreach ($newFiles as $file) {
            switch ($file->getName()) {
                case 'folder':
                    $folders[] = (string)$file;
                    break;
                case 'file':
                default:
                    $files[] = (string)$file;
                    $containerParts = explode('/', dirname((string)$file));
                    $container = '';
                    foreach ($containerParts as $part) {
                        if (!empty($container)) {
                            $container .= DIRECTORY_SEPARATOR;
                        }
                        $container .= $part;
                        if (!in_array($container, $containers)) {
                            $containers[] = $container;
                        }
                    }
                    break;
            }
        }
        foreach ($oldFiles as $file) {
            switch ($file->getName()) {
                case 'folder':
                    if (!in_array((string)$file, $folders) && !in_array((string)$file, $containers)) {
                        $foldersDeleted[] = (string)$file;
                    }
                    break;
                case 'file':
                default:
                    if (!in_array((string)$file, $files) && !in_array((string)$file, $folders)) {
                        $filesDeleted[] = (string)$file;
                    }
                    break;
            }
        }
        return ['folders' => $foldersDeleted, 'files' => $filesDeleted];
    }

    /**
     * 解析文件
     * @param \SimpleXMLElement $element
     * @param string $client
     * @param \SimpleXMLElement $oldFiles
     * @return bool
     * @throws ErrorException
     * @throws \yii\base\Exception
     */
    public function parseFiles(\SimpleXMLElement $element, $client = null, $oldFiles = null)
    {
        if (!$element || !count($element->children())) {
            return false;
        }

        if (!is_string($client)) {
            $client = 'root';
        }
        $destination = $this->getPath('extension_' . $client);

        if ($oldFiles && ($oldFiles instanceof \SimpleXMLElement)) {
            $oldEntries = $oldFiles->children();
            if (count($oldEntries)) {
                $deletions = $this->findDeletedFiles($oldEntries, $element->children());
                foreach ($deletions['folders'] as $deletedFolder) {
                    FileHelper::removeDirectory($destination . '/' . $deletedFolder);
                }
                foreach ($deletions['files'] as $deletedFile) {
                    FileHelper::unlink($destination . '/' . $deletedFile);
                }
            }
        }

        foreach ($element->children() as $child) {
            $childType = $child->getName() === 'folder' ? 'folder' : 'file';
            if ($childType === 'folder') {
                $type = (string)$child->attributes()->type;
                switch ($type) {
                    case 'main':
                        $dest = $destination;
                        break;
                    case 'view':
                        $dest = $destination . '/views';
                        break;
                    case 'media':
                        $dest = $destination . '/media';
                        break;
                    default:
                        $dest = $destination . '/' . (string)$child;
                        break;
                }
                if ($child && is_dir($this->source . '/' . (string)$child)) {
                    $source = $this->source . '/' . (string)$child;
                } else {
                    $source = $this->source;
                }
                if ($dest) {
                    FileHelper::copyDirectory($source, $dest);
                }
            } else {
                $source = $this->source . '/' . (string)$child;
                $dest = $destination . '/' . (string)$child;
                if (is_file($source) && is_null($child->attributes()->theme)) {
                    FileHelper::createDirectory(dirname($dest));
                    copy($source, $dest);
                }
            }
        }
    }

    /**
     * 解析SQL
     * @param \SimpleXMLElement $element
     * @return bool
     * @throws \yii\db\Exception
     */
    public function parseSQL(\SimpleXMLElement $element)
    {
        if (!$element || !count($element->children())) {
            return false;
        }
        $db = Yii::$app->db->createCommand();
        foreach ($element->table as $table) {
            $action = (string)$table->attributes()->action;
            $tableName = '{{%' . (string)$table->attributes()->name . '}}';
            $tableOptions = (string)$table->attributes()->options;

            if ($action === 'create') {
                if (isset($table->column)) {
                    $columns = [];
                    foreach ($table->column as $column) {
                        if (isset($column->attributes()->name)) {
                            $columns[(string)$column->attributes()->name] = (string)$column->attributes()->type;
                        } else {
                            $columns[] = (string)$column->attributes()->type;
                        }
                    }
                    $db->createTable($tableName, $columns, $tableOptions)->execute();
                }
            } elseif ($action === 'drop') {
                $db->dropTable($tableName)->execute();
            }

            if (isset($table->index)) {
                foreach ($table->index as $index) {
                    $indexAction = (string)$index->attributes()->action;
                    $indexName = (string)$index->attributes()->name;
                    $indexColumns = trim((string)$index->attributes()->columns);
                    $indexUnique = (int)$index->attributes()->unique > 0 ? true : false;
                    if ($indexAction === 'create' && !empty($indexColumns)) {
                        $db->createIndex($indexName, $tableName, $indexColumns, $indexUnique)->execute();
                    }
                    if ($indexAction === 'drop') {
                        $db->dropIndex($indexName, $tableName)->execute();
                    }
                }
            }
        }
    }

    /**
     * Method to parse through a languages element of the installation manifest and take appropriate action.
     * @param \SimpleXMLElement $element The XML node to process
     * @param string $client
     * @return bool
     */
    public function parseMessages(\SimpleXMLElement $element, $client)
    {
        if (!$element || !count($element->children())) {
            return false;
        }
        $dest = Yii::getAlias('@' . $client);
        $source = $this->source;
        foreach ($element->message as $message) {
            $file = 'messages/' . $message->attributes()->tag . '/' . (string)$message;
            if (is_file($source . '/' . $file)) {
                copy($source . '/' . $file, $dest . '/' . $file);
            } elseif (is_file($source . '/' . $client . '/' . $file)) {
                copy($source . '/' . $client . '/' . $file, $dest . '/' . $file);
            }
        }
        return true;
    }

    /**
     * 模型操作数据库时产生的错误信息转化成字符串
     * @param array $errors 错误信息
     * @param string $glue 分隔符
     * @return string
     */
    public function errorsToString($errors, $glue = '<br>&emsp;&emsp;')
    {
        $errorString = '';
        foreach ($errors as $error) {
            $errorString .= $glue . implode($glue, $error);
        }
        return $errorString;
    }

    public function removeFiles($element, $client = null)
    {
        if (!$element || !count($element->children())) {
            return true;
        }

        $retval = true;

        $files = $element->children();

        if (count($files) === 0) {
            return true;
        }

        $folder = '';

        if (!is_null($client)) {
            $client = Clients::find()->where(['name' => $client])->one();
        }

        switch ($element->getName()) {
            case 'media':
                if ((string)$element->attributes()->destination) {
                    $folder = (string)$element->attributes()->destination;
                }
                $source = Yii::getAlias('@root/common/media/' . $folder);
                break;
            case 'messages':
                $msgClient = (string)$element->attributes()->client;
                if ($msgClient) {
                    $client = Clients::find()->where(['name' => $msgClient])->one();
                    $source = Yii::getAlias('@root' . $client->path . '/messages');
                } else {
                    $source = is_null($client) ? '' : Yii::getAlias('@root' . $client->path . '/messages');
                }
                break;
            default :
                $source = $this->getPath('extension_' . (is_null($client) ? 'root' : $client->name));
                break;
        }

        foreach ($files as $file) {
            if ($file->getName() === 'message' && (string)$file->attributes()->tag !== '') {
                $tag = (string)$file->attributes()->tag;
                if ($source) {
                    $path = $source . '/' . $tag . '/' . basename((string)$file);
                } else {
                    $targetClient = Clients::find()->where(['name' => (string)$file->attributes()->client])->one();
                    $path = Yii::getAlias('@root') . $targetClient->path . '/messages/' . $tag . '/' . basename((string)$file);
                }
                if (!is_dir(dirname($path))) {
                    continue;
                }
            } else {
                $path = $source . '/' . $file;
            }

            try {
                if (is_dir($path)) {
                    FileHelper::removeDirectory($path);
                } else {
                    FileHelper::unlink($path);
                }
            } catch (ErrorException $e) {
                $errorMsg = Yii::t('installer', 'FAILED_DELETE_' . (is_dir($path) ? 'DIR' : 'FILE'), Helper::getRelativePath($path));
                Yii::$app->session->addFlash('error', $errorMsg);
                Yii::warning($e->getMessage(), 'installer');
                $retval = false;
            }
        }

        if (!empty($folder)) {
            FileHelper::removeDirectory($folder);
        }

        return $retval;
    }
}
